package cn.tianyuhan.util;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.RuntimeUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

import java.io.File;
import java.util.*;

/**
 * @ClassName TransUtil
 * @Description
 * @Author TianYuhan
 * @Date 2020-10-09 16:06
 * @Version 1.0
 **/
@Builder
@Getter
@Setter
public class FileHandleUtil {

//    static {
//        File file = new File("/usr/bin/ffmpeg");
//        FileUtil.writeFromStream(
//                FileHandleUtil.class.getClassLoader().getResourceAsStream("ffmpeg"),
//                file);
//    }

    private static final String FFMPEG = "ffmpeg";
    private static final String INPUT = "-i";
    private static final String ALWAYS_YES = "-y";
    private static final String SET_RATE = "-ar";
    private static final String SET_CHANNEL = "-ac";
    private static final String SET_BIT_RATE = "-ab";
    private static final String SET_THREADS = "-threads";
    private static final String ERROR_PRINT = "error";
    private static final String MAP_CHANNEL = "-map_channel";
    private static final String LEFT_OPTION = "0.0.0";
    private static final String RIGHT_OPTION = "0.0.1";
    private static final String SET_ACODEC = "-acodec";

    private static final String CUT_TIMESTAMP_TEMPLATE = "ffmpeg -y -v error -i {} -ss {} -c copy -to {} {}";
    private static final String CUT_DURATION_TEMPLATE = "ffmpeg -y -v error -i {} -ss {} -c copy -t {} {}";

    private static final String PASTE_TEMPLATE = "ffmpeg -y -v error -f concat -safe 0 -i {} -c copy {}";

    private String channel;
    private String sampleRate;
    private String bitRate;
    private String threads;
    private String acodec;
    private FileInfoUtil fileInfoUtil;

    /**
     * @Description: 根据文件列表进行文件粘贴
     * @param sourcePath
     * @param targetPath
     * @return
     */
    public static boolean pasteByFileList(String sourcePath, String targetPath) {
        String command = StrUtil.format(PASTE_TEMPLATE, sourcePath, targetPath);
        return execCommand(command);
    }

    /**
     * @Description: 按照时间戳截取文件
     * @param sourcePath
     * @param begin
     * @param end
     * @return
     */
    public static boolean cutByTimeStamp(String sourcePath, String targetPath, String begin, String end) {
        return generateCutCommand(sourcePath, targetPath, begin, end, CUT_TIMESTAMP_TEMPLATE);
    }

    /**
     * @Description: 按照开始时间和持续时间截取文件
     * @param sourcePath
     * @param begin
     * @param duration
     * @return
     */
    public static boolean cutByDuration(String sourcePath, String targetPath, String begin, String duration) {
        return generateCutCommand(sourcePath, targetPath, begin, duration, CUT_DURATION_TEMPLATE);
    }

    private static boolean generateCutCommand(String sourcePath, String targetPath, String begin, String duration, String cutDurationTemplate) {
        String command = StrUtil.format(cutDurationTemplate, sourcePath, begin, duration, targetPath);
        return execCommand(command);
    }

    private static boolean execCommand(String command) {
        String execResult = RuntimeUtil.execForStr(command);
        System.out.println(execResult);
        if (!(StrUtil.isEmpty(execResult) || StrUtil.isBlank(execResult))) {
            throw new RuntimeException(StrUtil.format("切割失败:{}", execResult));
        } else {
            return true;
        }
    }

    /***
     * @Description 音频转换
     * @param sourcePath
     * @param targetPath
     * @return boolean 是否转换成功
     */
    public boolean trans(String sourcePath, String targetPath) {
        StringBuilder stringBuilder = new StringBuilder();
        String execParams = StrUtil.concat(true, FFMPEG, " ", ALWAYS_YES, " ", "-v ", ERROR_PRINT, " ", INPUT, " ", sourcePath, " ");
        stringBuilder.append(execParams);
        if (StrUtil.isNotBlank(acodec)) {
            stringBuilder.append(StrUtil.concat(true, SET_ACODEC, " ", acodec, " "));
        }
        if (StrUtil.isNotBlank(channel)) {
            stringBuilder.append(StrUtil.concat(true, SET_CHANNEL, " ", channel, " "));
        }
        if (StrUtil.isNotBlank(sampleRate)) {
            stringBuilder.append(StrUtil.concat(true, SET_RATE, " ", sampleRate, " "));
        }
        if (StrUtil.isNotBlank(bitRate)) {
            stringBuilder.append(StrUtil.concat(true, SET_BIT_RATE, " ", bitRate, " "));
        }
        if (StrUtil.isNotBlank(threads)) {
            stringBuilder.append(StrUtil.concat(true, SET_THREADS, " ", threads, " "));
        }
        stringBuilder.append(targetPath);
        String execResult = RuntimeUtil.execForStr(
                stringBuilder.toString().split(" ")
        );
        if (!(StrUtil.isEmpty(execResult) || StrUtil.isBlank(execResult))) {
            throw new RuntimeException(StrUtil.format("转换失败:{}", execResult));
        } else {
            return true;
        }
    }

    /***
     * @Description 音频分割
     * @param sourcePath
     * @return targetLeftPath targetRightPath
     * @throws RuntimeException 分割失败 非正常的音频文件
     */
    public String[] split(String sourcePath) {
        if (checkAndTrans(sourcePath)) {
            File file = new File(sourcePath);
            String leftPath = StrUtil.format(
                    "{}/{}-left.{}",
                    FileUtil.getParent(file, 1),
                    FileUtil.getPrefix(file),
                    FileUtil.getSuffix(file));
            String rightPath = StrUtil.format(
                    "{}/{}-right.{}",
                    FileUtil.getParent(file, 1),
                    FileUtil.getPrefix(file),
                    FileUtil.getSuffix(file));
            if (split(sourcePath, leftPath, rightPath, false)) {
                return new String[]{leftPath, rightPath};
            } else {
                throw new RuntimeException("分割失败");
            }
        } else {
            throw new RuntimeException("非正常的音频文件");
        }
    }

    /***
     * @Description 指定左右声道路径
     * @param sourcePath
     * @param targetLeftPath
     * @param targetRightPath
     * @return boolean
     */
    public boolean split(String sourcePath, String targetLeftPath, String targetRightPath) {
        return split(sourcePath, targetLeftPath, targetRightPath, true);
    }

    /***
     * @param sourcePath
     * @param targetLeftPath
     * @param targetRightPath
     * @param needCheck
     * @return
     */
    private boolean split(String sourcePath, String targetLeftPath, String targetRightPath, boolean needCheck) {
        if (needCheck) {
            if (!checkAndTrans(sourcePath)) {
                throw new RuntimeException("强制检查和转换失败");
            }
        }
        String execResult = RuntimeUtil.execForStr(
                FFMPEG, ALWAYS_YES, "-v", ERROR_PRINT,
                INPUT, sourcePath,
                MAP_CHANNEL, LEFT_OPTION, targetLeftPath,
                MAP_CHANNEL, RIGHT_OPTION, targetRightPath);
        if (!(StrUtil.isEmpty(execResult) || StrUtil.isBlank(execResult))) {
            throw new RuntimeException(StrUtil.format("分轨失败:{}", execResult));
        } else {
            return true;
        }
    }

    /***
     * @Description 分割前需要判断声道数
     * @param file
     * @return
     */
    private int getChannelInfo(File file) {
        this.fileInfoUtil = new FileInfoUtil(file);
        return fileInfoUtil.getChannel();
    }

    /***
     * @Description 分割前检查声道数，如有必要进行转换
     * @param sourcePath
     * @return
     */
    private boolean checkAndTrans(String sourcePath) {
        int channel;
        File file = new File(sourcePath);
        if (this.fileInfoUtil != null) {
            channel = this.fileInfoUtil.getChannel();
        } else {
            channel = getChannelInfo(file);
        }
        if (channel == 2) {
            return true;
        } else {
            this.channel = "2";
            String targetPath = StrUtil.format(
                    "{}/{}-tmp.{}",
                    FileUtil.getParent(file, 1),
                    FileUtil.getPrefix(file),
                    FileUtil.getSuffix(file));
            if (trans(sourcePath, targetPath)) {
                File targetFile = new File((targetPath));
                FileUtil.move(targetFile, file, false);
                this.fileInfoUtil = new FileInfoUtil(targetFile);
                return true;
            } else {
                throw new RuntimeException("强制转换失败");
            }
        }
    }
}
